package com.wtf.dsp.admin.service.impl;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.wtf.dsp.admin.config.CustomMybatisConfiguration;
import com.wtf.dsp.admin.core.model.DataSourceInfo;
import com.wtf.dsp.admin.core.parser.ParamNameResolver;
import com.wtf.dsp.admin.core.parser.SqlParser;
import com.wtf.dsp.admin.core.provider.AbstractMybatisConfigProvider;
import com.wtf.dsp.admin.entity.WtfDspSql;
import com.wtf.dsp.admin.entity.WtfDspSqlLog;
import com.wtf.dsp.admin.enums.CommonErrorEnum;
import com.wtf.dsp.admin.enums.SqlMybatisXml;
import com.wtf.dsp.admin.exception.WtfDspException;
import com.wtf.dsp.admin.mapper.WtfDspSqlLogMapper;
import com.wtf.dsp.admin.mapper.WtfDspSqlMapper;
import com.wtf.dsp.admin.model.PageSql;
import com.wtf.dsp.admin.model.dto.*;
import com.wtf.dsp.admin.model.vo.SqlVO;
import com.wtf.dsp.admin.service.SqlService;
import com.wtf.dsp.admin.util.MapperXmlUtil;
import com.wtf.dsp.admin.util.SqlInterceptor;
import com.wtf.dsp.core.constant.NsConstant;
import com.wtf.dsp.core.enums.SqlResponseMapper;
import com.wtf.dsp.core.enums.SqlType;
import com.wtf.dsp.core.enums.TableApi;
import com.wtf.dsp.core.util.PageUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * ClassName: MapperSqlServiceImpl
 * Description:
 * date: 2023/7/11 11:05
 *
 * @author linhuanyou
 * @version 1.0
 */
@Slf4j
@Service
public class SqlServiceImpl implements SqlService {

    @Resource
    private AbstractMybatisConfigProvider mybatisConfigProvider;

    @Resource
    private WtfDspSqlMapper wtfDspSqlMapper;

    @Resource
    private WtfDspSqlLogMapper wtfDspSqlLogMapper;

    @Override
    public PageUtils logPage(SqlLogPageDTO sqlLogPageDTO) {
        Page<?> page = PageHelper.startPage(sqlLogPageDTO.getPage(), sqlLogPageDTO.getLimit());
        LambdaQueryWrapper<WtfDspSqlLog> wtfMapperSqlLambdaQueryWrapper = new LambdaQueryWrapper<>();
        wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSqlLog::getNs, sqlLogPageDTO.getNs());
        if(StringUtils.isNotBlank(sqlLogPageDTO.getDs())){
            wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSqlLog::getDs, sqlLogPageDTO.getDs());
        }
        if(StringUtils.isNotBlank(sqlLogPageDTO.getMns())){
            wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSqlLog::getMns, sqlLogPageDTO.getMns());
        }
        if(StringUtils.isNotBlank(sqlLogPageDTO.getSqlId())){
            wtfMapperSqlLambdaQueryWrapper.like(WtfDspSqlLog::getSqlId, sqlLogPageDTO.getSqlId());
        }
        if(StringUtils.isNotBlank(sqlLogPageDTO.getTranId())){
            wtfMapperSqlLambdaQueryWrapper.like(WtfDspSqlLog::getTranId, sqlLogPageDTO.getTranId());
        }
        wtfMapperSqlLambdaQueryWrapper.orderByDesc(WtfDspSqlLog::getBeginTime);
        List<WtfDspSqlLog> list = wtfDspSqlLogMapper.selectList(wtfMapperSqlLambdaQueryWrapper);
        return new PageUtils(list, (int) page.getTotal(), sqlLogPageDTO.getLimit(), sqlLogPageDTO.getPage());
    }

    @Override
    public void saveSqlLog(String tranId,DsSqlDTO dsSqlDTO,boolean requestSuccess,Object result,Exception e,long beginTime,long endTime){
        WtfDspSqlLog wtfSqlLog = new WtfDspSqlLog();
        wtfSqlLog.setTranId(tranId);
        wtfSqlLog.setNs(dsSqlDTO.getNs());
        wtfSqlLog.setDs(dsSqlDTO.getDs());
        wtfSqlLog.setMns(dsSqlDTO.getMns());
        wtfSqlLog.setSqlId(dsSqlDTO.getSqlId());
        wtfSqlLog.setDbType(dsSqlDTO.getDbType().getDb());
        wtfSqlLog.setTableApi(dsSqlDTO.getTableApi() == null ? null :  dsSqlDTO.getTableApi().toString());
        wtfSqlLog.setSqlResponseMapper(dsSqlDTO.getSqlResponseMapper().toString());
        wtfSqlLog.setExcSql(SqlResponseMapper.PAGE.equals(dsSqlDTO.getSqlResponseMapper())? dsSqlDTO.getPageSql().getExecSql() : dsSqlDTO.getExcSql());
        wtfSqlLog.setSqlType(dsSqlDTO.getSqlType().toString());
        wtfSqlLog.setBeginTime(new Date(beginTime));
        if(requestSuccess){
            wtfSqlLog.setReturnData(JSON.toJSONString(result));
        } else {
            wtfSqlLog.setIsSuccess(0);
            wtfSqlLog.setErrMsg(ExceptionUtil.stacktraceToString(e,-1));
        }
        wtfSqlLog.setEndTime(new Date(endTime));
        wtfSqlLog.setExecTime((int) (endTime - beginTime));
        int save = wtfDspSqlLogMapper.insert(wtfSqlLog);
    }

    @Override
    public SqlVO info(SqlInfoDTO sqlInfoDTO) {
        LambdaQueryWrapper<WtfDspSql> wtfMapperSqlLambdaQueryWrapper = new LambdaQueryWrapper<>();
        wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSql::getNs, sqlInfoDTO.getNs());
        wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSql::getDs, sqlInfoDTO.getDs());
        wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSql::getMns, sqlInfoDTO.getMns());
        wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSql::getSqlId, sqlInfoDTO.getSqlId());
        WtfDspSql wtfDspSql = wtfDspSqlMapper.selectOne(wtfMapperSqlLambdaQueryWrapper);
        SqlVO sqlVO = new SqlVO();
        BeanUtils.copyProperties(wtfDspSql,sqlVO);
        sqlVO.setSqlXml(SqlMybatisXml.obtainSqlstatementXml(SqlType.valueOf(sqlVO.getSqlType()),sqlVO.getSqlId(),sqlVO.getSqlStatement()));
        return sqlVO;
    }

    @Override
    public Object test(SqlTestDTO sqlTestDTO) throws Exception {
        DataSourceInfo dataSource = mybatisConfigProvider.getDataSource(sqlTestDTO.getNs(), sqlTestDTO.getDs());
        CustomMybatisConfiguration cutomMybatisConfiguration = new CustomMybatisConfiguration();
        List<String> mnsSqlList = new ArrayList<>();
        mnsSqlList.add(SqlMybatisXml.obtainSqlstatementXml(sqlTestDTO.getSqlType(),sqlTestDTO.getSqlId(),sqlTestDTO.getSqlStatement()));
        String mapperXml = MapperXmlUtil.mapperXmlStr(NsConstant.mapperNamespace(sqlTestDTO.getNs(),sqlTestDTO.getDs(),sqlTestDTO.getMns()), mnsSqlList);
        String nsDsMnsSqlGroup = NsConstant.mapperNamespace(sqlTestDTO.getNs(),sqlTestDTO.getDs(),sqlTestDTO.getMns(), sqlTestDTO.getSqlId());
        mybatisConfigProvider.refreshMapperResource(sqlTestDTO.getNs(),sqlTestDTO.getDs(),sqlTestDTO.getMns(),mapperXml,cutomMybatisConfiguration);
        MappedStatement mappedStatement = cutomMybatisConfiguration.getMappedStatement(nsDsMnsSqlGroup);
        BoundSql boundSql = mappedStatement.getBoundSql(ParamNameResolver.getNamedParams(sqlTestDTO.getParams()));
        String testSql = SqlInterceptor.formatSql(cutomMybatisConfiguration, boundSql);
        DsSqlDTO dsSqlDTO = new DsSqlDTO();
        dsSqlDTO.setDbType(dataSource.getDbType());
        dsSqlDTO.setNs(dataSource.getNs());
        dsSqlDTO.setDs(dataSource.getDs());
        dsSqlDTO.setMns(sqlTestDTO.getMns());
        dsSqlDTO.setSqlId(sqlTestDTO.getSqlId());
        dsSqlDTO.setExcSql(testSql);
        dsSqlDTO.setSqlResponseMapper(sqlTestDTO.getSqlResponseMapper());
        if(SqlResponseMapper.PAGE.equals(sqlTestDTO.getSqlResponseMapper())){
            PageSql pageSql = new PageSql();
            pageSql.setPage(sqlTestDTO.getPage());
            pageSql.setLimit(sqlTestDTO.getLimit() );
            pageSql.setExecSql(SqlParser.dialectSql(testSql,sqlTestDTO.getPage(),sqlTestDTO.getLimit(),dataSource.getDbType()));
            pageSql.setTotalSql(SqlParserUtils.getOptimizeCountSql(true,null,pageSql.getExecSql()).getSql());
            dsSqlDTO.setPageSql(pageSql);
        }
        return mybatisConfigProvider.execute(dsSqlDTO);
    }

    @Override
    public PageUtils page(SqlPageDTO sqlPageDTO) {
        Page<?> page = PageHelper.startPage(sqlPageDTO.getPage(), sqlPageDTO.getLimit());
        LambdaQueryWrapper<WtfDspSql> wtfMapperSqlLambdaQueryWrapper = new LambdaQueryWrapper<>();
        wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSql::getNs, sqlPageDTO.getNs());
        if(StringUtils.isNotBlank(sqlPageDTO.getDs())){
            wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSql::getDs, sqlPageDTO.getDs());
        }
        if(StringUtils.isNotBlank(sqlPageDTO.getMns())){
            wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSql::getMns, sqlPageDTO.getMns());
        }
        if(StringUtils.isNotBlank(sqlPageDTO.getSqlId())){
            wtfMapperSqlLambdaQueryWrapper.like(WtfDspSql::getSqlId, sqlPageDTO.getSqlId());
        }
        wtfMapperSqlLambdaQueryWrapper.orderByDesc(WtfDspSql::getCreateTime);
        List<SqlVO> list = wtfDspSqlMapper.selectList(wtfMapperSqlLambdaQueryWrapper).stream().map(e->{
            SqlVO sqlVO = new SqlVO();
            BeanUtils.copyProperties(e,sqlVO);
            sqlVO.setSqlXml(SqlMybatisXml.obtainSqlstatementXml(SqlType.valueOf(sqlVO.getSqlType()),sqlVO.getSqlId(),sqlVO.getSqlStatement()));
            return sqlVO;
        }).collect(Collectors.toList());
        return new PageUtils(list, (int) page.getTotal(), sqlPageDTO.getLimit(), sqlPageDTO.getPage());
    }

    @Override
    public List<WtfDspSql> list(SqlListDTO sqlListDTO) {
        LambdaQueryWrapper<WtfDspSql> wtfMapperSqlLambdaQueryWrapper = new LambdaQueryWrapper<>();
        wtfMapperSqlLambdaQueryWrapper.eq(WtfDspSql::getMns, sqlListDTO.getMns());
        List<WtfDspSql> list = wtfDspSqlMapper.selectList(wtfMapperSqlLambdaQueryWrapper);
        return list;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int add(SqlAddDTO sqlAddDTO) {
        if(wtfDspSqlMapper.count(sqlAddDTO) > 0){
            throw new WtfDspException(CommonErrorEnum.MAPPER_SQL_EXIST);
        }
        WtfDspSql wtfMapperSql = new WtfDspSql();
        BeanUtils.copyProperties(sqlAddDTO,wtfMapperSql);
        wtfMapperSql.setSqlType(sqlAddDTO.getSqlType().toString());
        wtfMapperSql.setMd5(DigestUtil.md5Hex(wtfMapperSql.getSqlStatement()));
        int add = wtfDspSqlMapper.insert(wtfMapperSql);
        if(add > 0){
            mybatisConfigProvider.refreshMapperSqlConfig(sqlAddDTO.getNs(), sqlAddDTO.getDs(), sqlAddDTO.getMns());
        }
        return add;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int remove(SqlDelDTO sqlDelDTO) {
        LambdaQueryWrapper<WtfDspSql> wtfMapperSqlLambdaQueryWrapper1 = new LambdaQueryWrapper<>();
        wtfMapperSqlLambdaQueryWrapper1.eq(WtfDspSql::getNs, sqlDelDTO.getNs());
        wtfMapperSqlLambdaQueryWrapper1.eq(WtfDspSql::getDs, sqlDelDTO.getDs());
        wtfMapperSqlLambdaQueryWrapper1.eq(WtfDspSql::getMns, sqlDelDTO.getMns());
        wtfMapperSqlLambdaQueryWrapper1.in(WtfDspSql::getSqlId, sqlDelDTO.getSqlIds());
        int del = wtfDspSqlMapper.delete(wtfMapperSqlLambdaQueryWrapper1);
        if(del > 0){
            mybatisConfigProvider.refreshMapperSqlConfig(sqlDelDTO.getNs(), sqlDelDTO.getDs(), sqlDelDTO.getMns());
        }
        return del;
    }

    @Override
    public int update(SqlUpdateDTO sqlUpdateDTO) {
        LambdaQueryWrapper<WtfDspSql> wtfDspSqlLambdaQueryWrapper = new LambdaQueryWrapper<>();
        wtfDspSqlLambdaQueryWrapper.eq(WtfDspSql::getNs, sqlUpdateDTO.getNs());
        wtfDspSqlLambdaQueryWrapper.eq(WtfDspSql::getDs, sqlUpdateDTO.getDs());
        wtfDspSqlLambdaQueryWrapper.eq(WtfDspSql::getMns, sqlUpdateDTO.getMns());
        wtfDspSqlLambdaQueryWrapper.eq(WtfDspSql::getSqlId, sqlUpdateDTO.getSqlId());
        WtfDspSql oldSql = wtfDspSqlMapper.selectOne(wtfDspSqlLambdaQueryWrapper);
        WtfDspSql newSql = new WtfDspSql();
        newSql.setSqlStatement(sqlUpdateDTO.getSqlStatement());
        newSql.setMd5(DigestUtil.md5Hex(newSql.getSqlStatement()));
        int update = wtfDspSqlMapper.update(newSql, wtfDspSqlLambdaQueryWrapper);
        if(update > 0) {
            if(!newSql.getMd5().equals(oldSql.getMd5())){
                mybatisConfigProvider.refreshMapperSqlConfig(sqlUpdateDTO.getNs(), sqlUpdateDTO.getDs(), sqlUpdateDTO.getMns());
            }
        }
        return update;
    }
}
